package com.deltapunkt.proxy;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

import com.deltapunkt.proxy.Command.Type;

//client:connect(target)->auth(target)->xchg ; the target here is the ServerAuthFwdProxy
//server:auth(client)->connect(target)->xchg ; the client here is the ClientAuthFwdProxy
public class ClientAuthPortProxy implements Proxy {
	private final SocketChannel client;
	private SocketChannel target;
	private final BlockingDeque<Event> readQueue;
	private final AsyncServer server;
	private State state;
	private final SocketAddress targetAddress;
	private final StringBuilder serverChallenge;

	public ClientAuthPortProxy(AsyncServer server, SocketChannel client,
			SocketAddress targetAddress) {
		this.server = server;
		this.client = client;
		this.targetAddress = targetAddress;
		readQueue = new LinkedBlockingDeque<Event>(10);
		state = new State("init");
		serverChallenge = new StringBuilder();
	}

	public void read(Event evt) {
		try {
			readQueue.putLast(evt);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void write(Message m) {
		try {
			server.addMessage(m);
			server.addCommand(new Command(Type.SETOPS, m.getChannel(),
					SelectionKey.OP_WRITE));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void enableRead(SocketChannel s) {
		server.addCommand(new Command(Type.SETOPS, s, SelectionKey.OP_READ));
	}

	public synchronized void run() {
		if (state.is("init")) {
			state = new State("connecting");
			initTargetConnection();
		} else if (state.is("connecting")) {
			finishConnection();
			if (state.is("connected")) {
				enableRead(target);
			}
		} else if (state.is("connected")) {
			readChallenge();
		} else if (state.is("authenticating")) {
			readServerResponse();
		} else if (state.is("authenticated")) {
			forwardMessage();
		}
	}

	private void initTargetConnection() {
		try {
			target = SocketChannel.open();
			target.configureBlocking(false);
			target.connect(targetAddress);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		// add register command
		server.mapSocket(target, this);
		Command cmd = new Command(Command.Type.REGISTER, target,
				SelectionKey.OP_CONNECT);
		server.addCommand(cmd);
		// target == new Connection(destHost, destPort);
		// initiate here connection
		System.out.println("Target connection initiated " + target);
	}

	private void finishConnection() {
		Event e = readQueue.poll();
		if (e != null && e instanceof State) {
			state = (State) e;
		}
	}

	private void readChallenge() {
		Message m = (Message) readQueue.poll();
		// read until '\n' is received
		for (byte b : m.getContent()) {
			if (b != '\n') {
				serverChallenge.append((char) b);
			} else {
				System.out.println("challenge=" + serverChallenge);
				Double challenge = null;
				try {
					challenge = Double.parseDouble(serverChallenge.toString());
				} catch (NumberFormatException nfe) {
				}
				if (challenge == null) {
					state = new State("terminated");
					// invalid number received from server, thus terminating
					// connection

					// under normal circumstances should never get here
					terminateConnection(target);
					terminateConnection(client);
				} else {
					state = new State("authenticating");
					byte[] clientResponse = Secret.generate(challenge);
					// the server will immediately terminate the connection
					// if authentication failed
					// otherwise, everything should continue normally
					write(new Message(target, clientResponse));
				}
			}
		}
	}

	private void readServerResponse() {
		Message m = (Message) readQueue.poll();
		ByteBuffer bb = m.getContentAsBuffer();
		byte ack = bb.get();
		System.out.println("ACK===" + ((byte)ack));
		if (ack == (byte)0xFF) {
			System.out.println("--------------SUCCESS!");
			// we are now sure that the sever side proxy has successfully
			// authenticated us and
			// it is as well connected to the target
			state = new State("authenticated");
			if (bb.hasRemaining()) {
				System.out
						.println("!!!!!!!!!!!!!!!!!HAD remaining!!!!!!!!!!!!!!!!!");
				ByteBuffer newBuffer = ByteBuffer.allocate(bb.remaining());
				newBuffer.put(bb);
				m = new Message(target, newBuffer);
				processFromTarget(m);
			}
			enableRead(client);
		} else {
			System.out.println("--------------TERMINATION!");
			state = new State("terminated");
			terminateConnection(target);
			terminateConnection(client);
		}
	}

	private void forwardMessage() {
		Event e = readQueue.poll();
		if (e != null && e instanceof Message) {
			Message m = (Message) e;
			if (m != null) {
				if (m.getChannel() == client) {
					processFromClient(m);
				}
				if (m.getChannel() == target) {
					processFromTarget(m);
				}
			}
		}
	}

	private void terminateConnection(SocketChannel s) {
		server.addCommand(new Command(Type.TERMINATE, s));
		readQueue.clear();
	}

	private void processFromClient(Message m) {
		write(new Message(target, m.getContent()));
	}

	private void processFromTarget(Message m) {
		write(new Message(client, m.getContent()));
	}
}
